﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Routing;
using Lilium.Web.Mvc.EntityFilters;
using Lilium.Data.Repository;
using Lilium.Web.Mvc.Metadata;
using Lilium.Web.Mvc.Context;
using Lilium.Web.Mvc.State;
using Lilium.Collections;

namespace Lilium.Web.Mvc
{
    public static class ControllerExtensions
    {
        private const string ControllerStateTempDataKey = "__ControllerState_";

        public static IQueryable<TEntity> ApplyFilters<TEntity>(this ControllerBase controller, IQueryable<TEntity> entities)
        {
            if (controller == null) throw new ArgumentNullException("controller");
            if (entities == null) throw new ArgumentNullException("entities");

            var filteringService = GetService<IFilteringService>(controller, false);
            if (filteringService != null)
                return filteringService.ApplyFilters(entities);
            else
                return entities;
        }

        [Obsolete("Это надо делать срествами StateService")]
        public static IDictionary<object, object> GetControllerState(this ControllerBase controller)
        {	//TODO: было бы неплохо написать более умный алгоритм, который сохраняет состояние контроллера только если это необходимо.
            if (controller == null) throw new ArgumentNullException("controller");

            var controllerStateKey = GetControllerStateKey(controller);
            var state = controller.TempData[controllerStateKey] as IDictionary<object, object>;
            if (state != null)
            {	// если контроллер уже имеет состояние, сохраняем его.
                controller.TempData.Keep(controllerStateKey);
            }
            else
            {
                state = new Dictionary<object, object>();
                controller.TempData[controllerStateKey] = state;
            }
            return state;
        }

        public static IRepository<TEntity> GetRepository<TEntity>(this ControllerBase controller)
            where TEntity : class
        {
            var repositoryProvider = GetService<IRepositoryProvider>(controller, true);
            return repositoryProvider.GetRepository<TEntity>();
        }

        public static RouteValueDictionary GetCombinedRouteValuesWithState<TController>(
            this ControllerBase controller,
            Expression<Func<TController, ActionResult>> action,
            IDictionary<string, object> routeValues)
        {
            if (controller == null) throw new ArgumentNullException("controller");

            var combinedValues = action == null
                ? controller.GetService<IActionContextService>(true).GetCombinedRouteValues()
                : controller.GetService<IMetadataService>(true).GetActionRouteValues(action);

            var stateService = controller.GetService<IStateService>(false);
            if (stateService != null)
                stateService.SaveObjectsState(combinedValues);

            if (routeValues != null)
                combinedValues.Merge(routeValues);

            return combinedValues;
        }

        [Obsolete("Это надо делать срествами StateService")]
        public static TState GetControllerState<TState>(this ControllerBase controller, string key)
        {
            var controllerState = GetControllerState(controller);

            object stateValue;
            if (!controllerState.TryGetValue(key, out stateValue))
            {
                stateValue = default(TState);
            }

            return (TState)stateValue;
        }

        public static void SetControllerState<TState>(this ControllerBase controller, string key, TState value)
        {
            var controllerState = GetControllerState(controller);
            controllerState[key] = value;
        }

        public static TState GetControllerState<TState>(this ControllerBase controller, string key, Func<TState> fallback)
        {
            var controllerState = GetControllerState(controller);

            object stateValue;
            if (!controllerState.TryGetValue(key, out stateValue) && fallback != null)
            {
                stateValue = fallback();
                controllerState.Add(key, stateValue);
            }

            return (TState)stateValue;
        }

        public static object GetService(this ControllerBase controller, Type serviceType, bool required)
        {
            var serviceProvider = GetServiceProvider(controller);
            return serviceProvider.GetService(serviceType, required);
        }

        public static TService GetService<TService>(this ControllerBase controller, bool required)
        {
            var serviceProvider = GetServiceProvider(controller);
            return serviceProvider.GetService<TService>(required);
        }

        public static Lilium.IServiceProvider GetServiceProvider(this ControllerBase controller)
        {
            return GetEntityController(controller);
        }

        public static RedirectToRouteResult RedirectToAction<TController>(this TController controller, Expression<Func<TController, ActionResult>> action)
            where TController : Controller
        {
            var routeValues = ControllerExtensions.GetCombinedRouteValuesWithState(controller, action, null);

            return new RedirectToRouteResult(routeValues);
        }


        public static RedirectToRouteResult RedirectToAction<TController>(this ControllerBase controller, Expression<Func<TController, ActionResult>> action)
            where TController : Controller
        {
            var routeValues = ControllerExtensions.GetCombinedRouteValuesWithState(controller, action, null);

            return new RedirectToRouteResult(routeValues);
        }

        // Укальное имя испоьзуется для предотвращения неоднозначности при использовании функции без указания Generic параметров.
        public static RedirectToRouteResult RedirectToController<TSrcController, TDstController>(this TSrcController controller, Expression<Func<TDstController, ActionResult>> action)
            where TSrcController : Controller
            where TDstController : Controller
        {
            var routeValues = ControllerExtensions.GetCombinedRouteValuesWithState(controller, action, null);

            return new RedirectToRouteResult(routeValues);
        }

        private static EntityController GetEntityController(ControllerBase controller)
        {
            if (controller == null) throw new ArgumentNullException("controller");

            var entityController = controller as EntityController;
            if (entityController == null)
                throw new InvalidOperationException("Controller must be of type EntityController to obtain service provider");

            return entityController;
        }

        private static string GetControllerStateKey(ControllerBase controller)
        {
            if (controller == null) throw new ArgumentNullException("controller");

            return ControllerStateTempDataKey + controller.GetType().AssemblyQualifiedName;
        }
    }
}